package com.biwin.module.authority.starter.service.impl;

import com.biwin.common.api.constant.BwSystemConstant;
import com.biwin.common.app.exception.BwNotFoundException;
import com.biwin.module.authority.api.dto.MenuDto;
import com.biwin.module.authority.api.enums.BwMenuExceptionEnum;
import com.biwin.module.authority.api.service.IMenuService;
import com.biwin.module.authority.api.vo.MenuVo;
import com.biwin.module.authority.domain.entity.Menu;
import com.biwin.module.authority.domain.mapper.MenuMapper;
import com.biwin.module.authority.starter.repository.IMenuRepository;
import com.biwin.module.orm.starter.service.AbstractCurdService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.util.Objects;

/**
 * TODO
 *
 * @author biwin
 * @since 0.0.1
 */
@Service
public class MenuServiceImpl extends AbstractCurdService<Menu, MenuVo, Long> implements IMenuService<Menu, MenuVo,
        Long> {
    private final IMenuRepository menuRepository;
    private final MenuMapper menuMapper;

    public MenuServiceImpl(IMenuRepository menuRepository, MenuMapper menuMapper) {
        super(menuRepository);
        this.menuRepository = menuRepository;
        this.menuMapper = menuMapper;
    }

    @Override
    protected MenuVo toVo(Menu entity) {
        return menuMapper.toVo(entity);
    }

    @Override
    public MenuVo findByName(String menuName) {
        Assert.notNull(menuName, "menuName must be not null");
        Menu menu =
                menuRepository.findByName(menuName).orElseThrow(() -> new BwNotFoundException(BwMenuExceptionEnum.MENU_NOT_EXIST));
        return toVo(menu);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public MenuVo add(MenuDto menuDto) {
        Menu menu = menuMapper.toEntity(menuDto);
        if (Objects.nonNull(menuDto.getParentId())) {
            Menu parent = findByIdE(menuDto.getParentId());
            Assert.isTrue(!parent.getDeleted(), "parent menu must be not deleted");
            Assert.isTrue(parent.getEnabled(), "parent menu must be enabled");
            parent.setLeaf(false);

            menu.setParent(parent);
            menu.setMenuLevel(parent.getMenuLevel() + 1);
        } else {
            menu.setLeaf(true);
            menu.setMenuLevel(1);
        }
        // 占位,防止数据库抛 field must be not null
        menu.setMenuIdPath(BwSystemConstant.HORIZONTAL_LINE);
        menu.setMenuNamePath(BwSystemConstant.HORIZONTAL_LINE);
        menuRepository.saveAndFlush(menu);

        setMenuPaths(menu);
        menuRepository.save(menu);
        return toVo(menu);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public MenuVo update(MenuDto menuDto) {
        Menu menu = menuMapper.mergeEntity(menuDto, findByIdE(menuDto.getId()));
        if (Objects.nonNull(menuDto.getParentId())) {
            Menu parent = findByIdE(menuDto.getParentId());
            menu.setParent(parent);
            Assert.state(parent.getEnabled().equals(menu.getEnabled() || !menu.getEnabled()), "parent menu state is " +
                    "disabled");
        }
        menuRepository.saveAndFlush(menu);
        StringBuilder menuNamePath = new StringBuilder();
        buildMenuNamePath(menu, menuNamePath);
        menu.setMenuNamePath(menuNamePath.toString());
        menuRepository.save(menu);
        return toVo(menu);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean enable(Long id) {
        return enableMenu(id, true);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean disable(Long id) {
        return enableMenu(id, false);
    }

    private boolean enableMenu(Long id, boolean enabled) {
        Menu menu = super.findByIdE(id);
        menu.setEnabled(enabled);
        menuRepository.save(menu);
        return true;
    }

    private void setMenuPaths(Menu menu) {
        StringBuilder menuPath = new StringBuilder();
        buildMenuIdPath(menu, menuPath);
        menu.setMenuIdPath(menuPath.toString());
        menuPath.delete(0, menuPath.length());
        buildMenuNamePath(menu, menuPath);
        menu.setMenuNamePath(menuPath.toString());
    }

    /**
     * 根据传入的菜单对象,构造 menuIdPath
     *
     * @return
     */
    private void buildMenuIdPath(Menu menu, StringBuilder stringBuilder) {
        Assert.notNull(menu, "menu object must be not null");
        if (Objects.nonNull(menu.getParent())) {
            buildMenuIdPath(menu.getParent(), stringBuilder);
        }
        if (stringBuilder.length() > 0) {
            stringBuilder.append(BwSystemConstant.VERTICAL_LINE);
        }
        stringBuilder.append(menu.getId());
    }


    /**
     * 根据传入的菜单对象,构造 menuNamePath
     *
     * @return
     */
    private void buildMenuNamePath(Menu menu, StringBuilder stringBuilder) {
        Assert.notNull(menu, "menu object must be not null");
        if (stringBuilder.length() > 0) {
            stringBuilder.insert(0, BwSystemConstant.VERTICAL_LINE);
        }
        stringBuilder.insert(0, menu.getName());
        if (Objects.nonNull(menu.getParent())) {
            buildMenuNamePath(menu.getParent(), stringBuilder);
        }
    }
}
